package com.easyaop.core.matcher;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import com.easyaop.api.Context;
import com.easyaop.api.LoggerFactory;
import com.easyaop.api.advisor.Advisor;
import com.easyaop.api.pointcut.MethodObjectForBuddy;
import com.easyaop.api.pointcut.Pointcut;
import com.easyaop.commons.utils.PointcutUtils;

import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;

public class MethodElementMatcher /*implements ElementMatcher<MethodDescription>*/ {
    private static final Logger LOGGER = LoggerFactory.getLogger("MethodElementMatcher");

    static Map<Pointcut, Map<MethodDescription, Boolean>> cache = new ConcurrentHashMap<>();

    private TypeDescription typeDescription;

    private ClassLoader classLoader;

    public MethodElementMatcher(TypeDescription typeDescription, ClassLoader classLoader) {
        this.typeDescription = typeDescription;
        this.classLoader = classLoader;
    }

    //    @Override
    public boolean matches(MethodDescription methodDescription) {
        boolean result = false;
        for (Advisor advisor : Context.ADVISORS.toArray(new Advisor[0])) {
            Pointcut pointcut = advisor.getPointcut();

            if (!TypeElementMatcher.matches(typeDescription, pointcut)) {
                continue;
            }

            if (matches(methodDescription, pointcut)) {
                advisor.addSignature(getSignature(methodDescription));
                result = true;
            }
        }
        if (result) {
            LOGGER.info("matches method " + methodDescription.getDeclaringType() + " # " + methodDescription.getName());
        }
        return result;
    }
    public static String getSignature(MethodDescription methodDescription) {
        return PointcutUtils. getSignature(methodDescription.getDeclaringType().getActualName(), methodDescription.getName(),
                methodDescription.getDescriptor());
    }
    private static boolean matches0(MethodDescription methodDescription, Pointcut pointcut) {
        boolean isMethod = pointcut.isMethod(new MethodObjectForBuddy(methodDescription));
        LOGGER.info("isMethod pointcut(" + pointcut.hashCode() + ") " + methodDescription.getDeclaringType() + " # " + methodDescription.getName() + "->" + isMethod);
        return isMethod;
    }

    public static synchronized boolean matches(MethodDescription methodDescription, Pointcut pointcut) {
        Map<MethodDescription, Boolean> methodDescriptionBooleanMap = cache.computeIfAbsent(pointcut, (key) -> new ConcurrentHashMap<>());

        Boolean result = methodDescriptionBooleanMap.get(methodDescription);
        if (result != null) {
            LOGGER.info("isMethod(cache) pointcut(" + pointcut.hashCode() + ") " + methodDescription.getName() + "->" + result);
            return result;
        }

        result = matches0(methodDescription, pointcut);
        methodDescriptionBooleanMap.put(methodDescription, result);

        return result;
    }
}
